//
//  ConnectionListener.m
//  Networking
//
//  Created by Christoph Eßer on 11/24/11.
//  Copyright 2011 __MyCompanyName__. All rights reserved.
//

#import "ConnectionListener.h"
#import "Connection.h"
#import <arpa/inet.h>
#import <netinet/in.h>
#import <sys/socket.h>

//is called when client connects
void acceptCallBack(CFSocketRef s, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
{
    assert(type == kCFSocketAcceptCallBack);
    assert(   (int *) data  != NULL );
    assert( (*(int *) data) != -1 );	
	
    NSLog(@"Accept callback called");
    
    ConnectionListener *refToSelf = (ConnectionListener*)info;
    //data is file descriptor (native socket handler)
    //set up new connection from native socket
	Connection *conn = [[Connection alloc] initWithNativeSocket: *((CFSocketNativeHandle*) data)];
    //send new connection to ServerThreadController
    [[refToSelf stc] newIncomingConnection: conn];
    [conn release];
}

@implementation ConnectionListener
@synthesize stc;

-(id) initWithSTC: (ServerThreadController*) controller andPort: (NSInteger) port {
    self = [super init];
    
    NSLog(@"Starting connection listener on port %ld",port);
    
    if(self) {
        //setReferences
        stc = controller;
        
        //From here on: set up network socket
        struct sockaddr_in stSockAddr;
        memset(&stSockAddr, 0, sizeof(stSockAddr));
        
        int socketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
        
        if (socketFD == -1) {
            [NSException raise:@"SocketCreationException" format:(@"error creating server socket")];
            return nil;
        }
        
        // set SO_REUSEADDR on socket to true, making it possible to restart the server after a crash
        const int int_true = 1;
        setsockopt(socketFD, SOL_SOCKET, SO_REUSEADDR, &int_true, sizeof int_true);
        
        stSockAddr.sin_family = AF_INET;
        stSockAddr.sin_port = htons(port);
        stSockAddr.sin_addr.s_addr = INADDR_ANY;
        
        if(-1 == bind(socketFD,(struct sockaddr *)&stSockAddr, sizeof(stSockAddr)) ) {
            [NSException raise:@"SocketCreationException" format:@"error binding server port (%ld) to socket",port];
            close(socketFD);
            return nil;
        }
        
        if(-1 == listen(socketFD, 10))  {
            [NSException raise:@"SocketCreationException" format:@"could not listen on socket (port %ld)",port];
            close(socketFD); // use better error handling, send back sth. to main thread and display dialog...
            return nil;
        }
        
        //create socket context to provide self as info variable in callback
        CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};
        
        //create CFSocket and insert CallBack to Runloop
        socketCF = CFSocketCreateWithNative(NULL, (CFSocketNativeHandle) socketFD, kCFSocketAcceptCallBack, acceptCallBack, &socketCtxt);
        CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(NULL, socketCF, 0);
        CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
        CFRelease(source);
    }
    
    return self;
}

-(void) dealloc {
    CFSocketInvalidate(socketCF);
    CFRelease(socketCF);
}

@end
